#include "file.h"

#include "thread.h"
#include "stdio_kernel.h"
#include "bitmap.h"
#include "ide.h"
#include "super_block.h"
#include "fs.h"
#include "inode.h"
#include "dir.h"
#include "string.h"
#include "interrupt.h"
#include "debug.h"
#include "global.h"

struct file file_table[MAX_FILE_OPEN];

/* 从file_table中获得可用file项 */
int32_t get_free_slot_in_global()
{
    uint32_t file_idx = 3;
    while(file_idx < MAX_FILE_OPEN) {
        if(file_table[file_idx].file_inode == NULL) {
            break;
        }
        file_idx++;
    }

    if (file_idx == MAX_FILE_OPEN)
    {
        printk("exceed max open files\n"); 
        return -1;
    }

    return file_idx;
}

/* 从进程或线程fd_table中, 获得可用文件描述符. fd_table单元保存file_table下标 */
int32_t pcb_fd_install(int32_t global_file_idx) 
{
    struct task_struct* cur = running_thread();
    uint8_t local_fd_idx = 3;
    while(local_fd_idx < MAX_FILES_OPEN_PER_PROC) {
        if(cur->fd_table[local_fd_idx] == -1) {
            cur->fd_table[local_fd_idx] = global_file_idx;
            break;
        }
        local_fd_idx++;
    }

    if (local_fd_idx == MAX_FILES_OPEN_PER_PROC)
    {
        printk("exceed max open files_per_proc\n"); 
        return -1;
    }

    return local_fd_idx;
}

/* 从inode_bitmap中, 分配一个空闲inode */
int32_t inode_bitmap_alloc(struct partition* part)
{
    int32_t bit_idx = bitmap_scan(&part->inode_bitmap, 1);
    if(bit_idx == -1) {
        return -1;
    }
    bitmap_set(&part->inode_bitmap, bit_idx, 1);
    return bit_idx;
}

/* 从block_bitmap中, 分配一个空闲扇区, 返回扇区地址 */
int32_t block_bitmap_alloc(struct partition* part)
{
    int32_t bit_idx = bitmap_scan(&part->block_bitmap, 1);
    if(bit_idx == -1) {
        return -1;
    }
    bitmap_set(&part->block_bitmap, bit_idx, 1);
    return (part->sb->data_start_lba + bit_idx); 
}

/* 同步硬盘的bitmap. 虽只有1bit, 但硬盘以一块[这里为512B]读写*/
void bitmap_sync(struct partition* part, uint32_t bit_idx, enum bitmap_type btmp)
{
    uint32_t off_sec = bit_idx / 8 / 512;
    uint32_t off_size = off_sec * BLOCK_SIZE;

    uint32_t sec_lba = 0;
    uint8_t* bitmap_off = NULL;

    switch(btmp) {
        case INODE_BITMAP:
            sec_lba = part->sb->inode_bitmap_lba + off_sec;  // 硬盘中要同步的扇区
            bitmap_off = part->inode_bitmap.bits + off_size; // 内存待写入硬盘的字节位置
            break;
        case BLOCK_BITMAP:
            sec_lba = part->sb->block_bitmap_lba + off_sec;  // 硬盘中要同步的扇区
            bitmap_off = part->block_bitmap.bits + off_size; // 内存待写入硬盘的字节位置
            break;
    }

    ide_write(part->my_disk, sec_lba, bitmap_off, 1);
}

/* 创建普通文件, 返回该文件的文件描述符 */
int32_t file_create(struct dir* parent_dir, char* filename, uint32_t flag)
{
    int32_t rollback_step = 0;
// 1. 申请新inode, 新block. 新file一些属性保存在file_table数组可用单元
    int32_t inode_no = inode_bitmap_alloc(cur_part);
    if(inode_no == -1) {
        printk("in file_create: allocate inode failed\n");
        return -1;
    }

    struct inode* new_file_inode = (struct inode*)sys_malloc(sizeof(struct inode));
    if(new_file_inode == NULL) { 
        printk("file_create: sys_malloc for inode failed\n");
        rollback_step = 1;
        goto rollback;
    }
    inode_init(inode_no, new_file_inode);

    int file_idx = get_free_slot_in_global();
    if(file_idx == -1) {
        printk("file_create: exceed max open files failed\n");
        rollback_step = 2;
        goto rollback;
    }
    file_table[file_idx].file_inode = new_file_inode;
    file_table[file_idx].file_pos = 0;
    file_table[file_idx].file_flags = flag;
    file_table[file_idx].file_inode->write_deny = false;

// 2. 为父目录添加本文件的目录项
    struct dir_entry new_dir_entry;
    memset(&new_dir_entry, 0, sizeof(struct dir_entry));
    create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry);

// 3. 同步到硬盘
    void* io_buf = sys_malloc(1024);
    if(io_buf == NULL) {
        printk("in file_create: sys_malloc for io_buf failed\n");
        return -1;
    }
    // 同步父目录的目录项
    if(!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
        printk("file_create: sync_dir_entry failed\n");
        rollback_step = 3;
        goto rollback;
    }
    // 同步父目录的inode
    memset(io_buf, 0, 1024);
    inode_sync(cur_part, parent_dir->inode, io_buf);
    // 同步该文件的inode
    memset(io_buf, 0, 1024);
    inode_sync(cur_part, new_file_inode, io_buf);
    // 同步inode_bitmap位图
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);
    
    list_push(&cur_part->open_inodes, &new_file_inode->inode_tag);
    new_file_inode->i_open_cnts = 1;

    sys_free(io_buf);
    return pcb_fd_install(file_idx); // 返回文件描述符

    rollback:
        switch(rollback_step) {
            case 3:
                memset(&file_table[file_idx], 0, sizeof(struct file));
            case 2:
                sys_free(new_file_inode);
            case 1:
                // 新文件inode创建失败, 恢复inode_bitmap
                bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
                break;
        }
    sys_free(io_buf);
    return -1;
}

/* 打开inode_no的inode文件, inode->open_cnt++. 成功返回文件描述符, 失败返回-1 */
int32_t file_open(uint32_t inode_no, enum oflags flag)
{
    int file_idx = get_free_slot_in_global();
    if(file_idx == -1) {
        printk("exceed max open files\n");
        return -1;
    }
    file_table[file_idx].file_flags = flag;
    file_table[file_idx].file_inode = inode_open(cur_part, inode_no);
    file_table[file_idx].file_pos = 0;    // 每次打开文件, 重置file_pos
    bool* write_deny = &file_table[file_idx].file_inode->write_deny;
    
    if(flag&O_WRONLY || flag&O_RDWR) { // 该文件可写, 避免其他进程同时写
        enum intr_status old_status = intr_disable();
        if(!(*write_deny)) {
            *write_deny = true;
            intr_set_status(old_status);
        } else {
            intr_set_status(old_status);
            printk("file cant be write now, try again later\n");
            return -1;
        }
    }
    return pcb_fd_install(file_idx);
}

int32_t file_close(struct file* file) 
{
    if(file == NULL) {
        return -1;
    }
    file->file_inode->write_deny = false;
    inode_close(file->file_inode);
    file->file_inode = NULL;
    return 0;
}

/* 把buf中的count字节写入file, 成功返回写入字节数, 失败返回-1 */
int32_t file_write(struct file *file, const void *buf, uint32_t count)
{
    /* 0. 准备, 检查 */
    if((file->file_inode->i_size + count) > (BLOCK_SIZE*140)) {
        printk("file_write: exceed max file_size 512*140B, write file failed\n");
        return -1;
    }

    uint8_t *io_buf = sys_malloc(512);
    if(io_buf == NULL) {
        printk("file_write: sys_malloc io_buf failed\n");
        return -1;
    }

    // 把要写入数据的块地址, 收集到all_blocks
    uint32_t *all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE + 4*12);
    if(all_blocks == NULL) {
        printk("file_write: sys_malloc all_blocks failed\n");
        return -1;
    }

    uint32_t file_has_used_blocks = DIV_ROUND_UP(file->file_inode->i_size, BLOCK_SIZE);
    uint32_t file_will_used_blocks = DIV_ROUND_UP(file->file_inode->i_size + count , BLOCK_SIZE);
    ASSERT(file_will_used_blocks <= 12 + 512/4);
    uint32_t add_blocks = file_will_used_blocks - file_has_used_blocks;

    uint32_t block_idx = 0;
    uint32_t block_lba = -1;
    uint32_t indirect_block_table=0;
    uint32_t block_bitmap_idx;
    if (add_blocks > 0) {
    /* 2. 要分配新数据块, 把数据块地址收集到all_blocks中 */
        // 新数据块前12, 不通过间接块表
        if (file_will_used_blocks <= 12) {
            // 若不是空文件, 把原先末用完的数据块地址, 赋给all_blocks
            if(file_has_used_blocks != 0) {
                block_idx = file_has_used_blocks - 1;
                all_blocks[block_idx] = file->file_inode->i_sectors[block_idx];
            } else {
                block_idx = -1;
            }
            // 申请足够blocks, 并同步block_bitmap
            while(add_blocks--) {
                block_idx++;
                int32_t block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc\n");
                    return -1;
                }
                ASSERT(file->file_inode->i_sectors[block_idx] == 0);
                file->file_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;

                uint32_t block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
            }
        }
        // FIXME:新数据块后12, 通过间接块表
        else if (file_has_used_blocks <= 12 && file_will_used_blocks > 12)
        {
            /* 第二种情况: 旧数据在12个直接块内,新数据将使用间接块*/

            /* 先将有剩余空间的可继续用的扇区地址收集到all_blocks */
            block_idx = file_has_used_blocks - 1; // 指向旧数据所在的最后一个扇区
            all_blocks[block_idx] = file->file_inode->i_sectors[block_idx];

            /* 创建一级间接块表 */
            block_lba = block_bitmap_alloc(cur_part);
            if (block_lba == -1)
            {
                printk("file_write: block_bitmap_alloc for situation 2 failed\n");
                return -1;
            }

            //ASSERT(file->fd_inode->i_sectors[12] == 0); // 确保一级间接块表未分配
            /* 分配一级间接块索引表 */
            indirect_block_table = file->file_inode->i_sectors[12] = block_lba;

            block_idx = file_has_used_blocks; // 第一个未使用的块,即本文件最后一个已经使用的直接块的下一块
            while (block_idx < file_will_used_blocks)
            {
                block_lba = block_bitmap_alloc(cur_part);
                if (block_lba == -1)
                {
                    printk("file_write: block_bitmap_alloc for situation 2 failed\n");
                    return -1;
                }

                if (block_idx < 12)
                {                                                      // 新创建的0~11块直接存入all_blocks数组
                    file->file_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
                }
                else
                { // 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘
                    all_blocks[block_idx] = block_lba;
                }

                /* 每分配一个块就将位图同步到硬盘 */
                block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                block_idx++; // 下一个新扇区
            }
            ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 同步一级间接块表到硬盘
        }
        else if (file_has_used_blocks > 12)
        {
            /* 第三种情况:新数据占据间接块*/
            //ASSERT(file->fd_inode->i_sectors[12] != 0);           // 已经具备了一级间接块表
            indirect_block_table = file->file_inode->i_sectors[12]; // 获取一级间接表地址

            /* 已使用的间接块也将被读入all_blocks,无须单独收录 */
            ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 获取所有间接块地址

            block_idx = file_has_used_blocks; // 第一个未使用的间接块,即已经使用的间接块的下一块
            while (block_idx < file_will_used_blocks)
            {
                block_lba = block_bitmap_alloc(cur_part);
                if (block_lba == -1)
                {
                    printk("file_write: block_bitmap_alloc for situation 3 failed\n");
                    return -1;
                }
                all_blocks[block_idx++] = block_lba;

                /* 每分配一个块就将位图同步到硬盘 */
                block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
            }
            ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 同步一级间接块表到硬盘
        }
    } else {
    /* 3. 不要分配新数据块 */
        if(file_will_used_blocks <= 12) {
            uint32_t block_idx = file_has_used_blocks - 1;
            all_blocks[block_idx] = file->file_inode->i_sectors[block_idx];
        } else {
            ASSERT(file->file_inode->i_sectors[12] != 0);
            uint32_t idx_block_table_idx = file->file_inode->i_sectors[12]; 
            ide_read(cur_part->my_disk, idx_block_table_idx, all_blocks+12, 1);
        }
    }
    
    /* 4. 开始把数据写入硬盘中 */
    uint32_t written_bytes = 0;
    uint32_t left_bytes = count;
    const uint8_t* src = buf;
    bool first_write_block = true;
    file->file_pos = file->file_inode->i_size-1;

    while(left_bytes) {
        memset(io_buf, 0, BLOCK_SIZE);
        uint32_t block_idx = file->file_inode->i_size / BLOCK_SIZE;
        uint32_t sec_lba = all_blocks[block_idx];
        uint32_t used_bytes_in_block = file->file_inode->i_size % BLOCK_SIZE;
        uint32_t left_bytes_in_block = BLOCK_SIZE - used_bytes_in_block;
        uint32_t chunk_size = left_bytes > left_bytes_in_block ? left_bytes_in_block : left_bytes;
        if(first_write_block) {
            ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
            first_write_block = false;
        }
        memcpy(io_buf+used_bytes_in_block, src, chunk_size);
        ide_write(cur_part->my_disk, sec_lba, io_buf, 1);

        // TODO: 测试
        printk("file write at lba 0x%x\n", sec_lba);

        src += chunk_size;
        file->file_inode->i_size += chunk_size;
        file->file_pos += chunk_size;
        left_bytes -= chunk_size;
        written_bytes += chunk_size;
    }

    inode_sync(cur_part, file->file_inode, io_buf);
    sys_free(all_blocks);
    sys_free(io_buf);
    return written_bytes;
}

/* 从file中读取count字符, 保存在buf中. 成功, 返回读取字符数; 失败, 返回-1 */
int32_t file_read(struct file *file, void *buf, uint32_t count)
{
    uint8_t *buf_dst = (uint8_t*)buf;
    uint32_t will_read_size = count;
    uint32_t left_size = will_read_size;
    if((file->file_pos + count) > file->file_inode->i_size) {
        will_read_size = file->file_inode->i_size - file->file_pos;
        left_size = will_read_size;
        if(will_read_size == 0) {
            return -1;
        }
    }

    uint8_t *io_buf = sys_malloc(BLOCK_SIZE);
    if(io_buf==NULL) {
        printk("file_read: sysmalloc for io_buf failed\n");
        return -1;
    }

    uint32_t *all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE+48);
    if(all_blocks==NULL) {
        printk("file_read: sysmalloc for all_blocks failed\n");
        return -1;
    }

    uint32_t read_block_start_idx = file->file_pos / BLOCK_SIZE;
    uint32_t read_block_end_idx = (file->file_pos+will_read_size) / BLOCK_SIZE;
    uint32_t will_read_blocks = read_block_end_idx - read_block_start_idx;
    ASSERT(read_block_end_idx < 139 && read_block_start_idx < 139);

    uint32_t block_idx;
    uint32_t idx_block_table_idx;
    if(will_read_blocks == 0) {
    /* 当要读取数据, 没有跨越一个块 */
        ASSERT(read_block_end_idx == read_block_start_idx);
        if(read_block_start_idx < 12) {
            block_idx = read_block_start_idx;
            all_blocks[block_idx] = file->file_inode->i_sectors[block_idx];
        } else {
            idx_block_table_idx = file->file_inode->i_sectors[12];
            ide_read(cur_part->my_disk, idx_block_table_idx, all_blocks + 12, 1);
        }
    } else {
    /*当要读取多个块 */
    /* 第一种情况: 起始块和终止块属于直接块*/
    if (read_block_end_idx < 12)
    { // 数据结束所在的块属于直接块
        block_idx = read_block_start_idx;
        while (block_idx <= read_block_end_idx)
        {
            all_blocks[block_idx] = file->file_inode->i_sectors[block_idx];
            block_idx++;
        }
    }
    else if (read_block_start_idx < 12 && read_block_end_idx >= 12)
    {
        /* 第二种情况: 待读入的数据跨越直接块和间接块两类*/
        /* 先将直接块地址写入all_blocks */
        block_idx = read_block_start_idx;
        while (block_idx < 12)
        {
            all_blocks[block_idx] = file->file_inode->i_sectors[block_idx];
            block_idx++;
        }
        ASSERT(file->file_inode->i_sectors[12] != 0); // 确保已经分配了一级间接块表

        /* 再将间接块地址写入all_blocks */
        idx_block_table_idx = file->file_inode->i_sectors[12];
        ide_read(cur_part->my_disk, idx_block_table_idx, all_blocks + 12, 1); // 将一级间接块表读进来写入到第13个块的位置之后
    }
    else
    {
        /* 第三种情况: 数据在间接块中*/
        ASSERT(file->file_inode->i_sectors[12] != 0);                          // 确保已经分配了一级间接块表
        idx_block_table_idx = file->file_inode->i_sectors[12];                // 获取一级间接表地址
        ide_read(cur_part->my_disk, idx_block_table_idx, all_blocks + 12, 1); // 将一级间接块表读进来写入到第13个块的位置之后
    }
    }

    uint32_t sec_idx, sec_lba, sec_off_bytes, sec_left_bytes, chunk_size;
    uint32_t bytes_read = 0;

    while(bytes_read < will_read_size) {
        sec_idx = file->file_pos / BLOCK_SIZE;
        sec_lba = all_blocks[sec_idx];
        sec_off_bytes = file->file_pos % BLOCK_SIZE;
        sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
        chunk_size = left_size < sec_left_bytes ? left_size : sec_left_bytes;

        memset(io_buf, 0, BLOCK_SIZE);
        ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
        memcpy(buf_dst, io_buf+sec_off_bytes, chunk_size);

        buf_dst += chunk_size;
        file->file_pos += chunk_size;
        bytes_read += chunk_size;
        left_size -= chunk_size;
    }
    sys_free(all_blocks);
    sys_free(io_buf);
    return bytes_read;
}
